/*
 * 巴法科技:2022-11-22
 * 支持巴法app和小程序配网，长按按键可重新配网
 */
#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <EEPROM.h>
#include <Ticker.h>
#include <ArduinoJson.h>
#include <OneButton.h>
#include <httpUpdate.h>
#include <ESP32Ping.h>
#include <BLEDevice.h>
WiFiClient client_bemfa_WiFiClient;
HTTPClient http_bemfa_HTTPClient;
// 巴法云服务器地址默认即可
#define server_ip "bemfa.com" // 巴法云服务器地址默认即可
#define server_port "8344"    // 服务器端口，tcp创客云端口8344

//*****可以修改的地方******//

String verSion = "3";  // 1是mqtt协议，3是tcp协议,5是mqtt V2 版本，7是 tcp V2 版本
String aptype = "006"; // 001插座类型，002灯类型，003风扇类型，004传感器，005空调，006开关，009窗帘
String adminID = "";   // 默认空即可。企业id,建议企业用户配置，用户会自动绑定到该企业下，获取id方法见接入文档5.17节

#define k_Pin 19         // 大门开关引脚
#define k0_Pin 26        // 卷帘门开关上拉引脚
#define k1_Pin 27        // 卷帘门开门引脚
#define k2_Pin 25        // 卷帘门关门引脚
#define buttonPin 0      // 定义按钮引脚
#define StatePin 18      // 定义锁门状态引脚
bool State = false;      // 门状态
bool State1 = false;     // 锁状态
bool State2 = false;     // 回家状态
bool State3 = false;     // 开锁条件
hw_timer_t *tim1 = NULL; // 声明看门狗指针变量

// 最大字节数
#define MAX_PACKETSIZE 512
// 设置心跳值30s
#define KEEPALIVEATIME 2 * 1000
OneButton button(buttonPin, true);

BLEScan *pBLEScan;
String getb = "0000fdaa-0000-1000-8000-00805f9b34fb";

// tcp客户端相关初始化，默认即可
WiFiClient TCPclient;
String TcpClient_Buff = "";
int failCount = 0;            // 定义失败连接次数
bool preTCPConnected = false; // 初始化和服务器建立连接状态
unsigned int TcpClient_BuffIndex = 0;
unsigned long TcpClient_preTick = 0;
unsigned long preHeartTick = 0;    // 心跳
unsigned long preTCPStartTick = 0; // 连接

/**
 * 结构体，用于存储配网信息
 */
struct config_type
{
  uint8_t reboot;
  uint8_t magic;
  char stassid[32];
  char stapsw[64];
  char cuid[40];
  char ctopic[32];
  char ip_1[16];
  char ip_2[16];
};
config_type config;
uint8_t *p = (uint8_t *)(&config);

// 相关函数初始化
// 连接WIFI
void doWiFiTick();
void initWiFi();
void lanyasaomian();
void doSmartconfig();
void saveConfig();
void loadConfig();
void restoreFactory();
void waitKey();
void Autosuo();

// 按键函数
void click();       /******单击******/
void doubleclick(); /******双击******/
// void longPressStart(); /******长按开始******/
void duringLongPress(); /******长按期间******/
// void longPressStop();/******长按结束******/
// void attachPressStart();/***按下键就会持续触发***/

// TCP初始化连接
void doTCPClientTick();
void startTCPClient();
void sendtoTCPServer(String p);

// 门控制函数
void turnOnLed();
void turnOffLed();
void turnOnJlm();
void turnOffJlm();

int httpCode = 0;
char config_flag = 0;
String UID = "";
String TOPIC1 = "";
String TOPIC2 = "";
#define HOST_NAME "bemfa"

// 升级超时中断函数
void tim1Interrupt()
{
  sendtoTCPServer("cmd=2&uid=" + UID + "&topic=" + TOPIC1 + "&msg=升级超时系统重启\r\n"); // 推送消息
  // Serial.println("升级超时系统重启");
  delay(1000);
  esp_restart();
}
// 当升级开始时，打印日志
void update_started()
{
  // Serial.println("CALLBACK:  HTTP update process started");
}

// 当升级结束时，打印日志
void update_finished()
{
  // Serial.println("CALLBACK:  HTTP update process finished");
}

// 当升级中，打印日志
void update_progress(int cur, int total)
{
  timerWrite(tim1, 0); // 喂狗操作，计时器清零
  static bool update = false;
  int bfb = map(cur, 0, total, 0, 100);
  if (bfb % 5 == 0)
  {
    if (!update)
    {
      if (bfb == 100)
      {
        delay(1000);
      }
      sendtoTCPServer("cmd=2&uid=" + UID + "&topic=" + TOPIC1 + "&msg=" + TOPIC1 + "#" + String(bfb) + " % \r\n");
      update = true;
      Serial.printf("CALLBACK:  HTTP update process at %d of %d bytes... %d %\n", cur, total, bfb);
    }
  }
  else
  {
    update = false;
  }
}

// 当升级失败时，打印日志
void update_error(int err)
{
  // Serial.printf("CALLBACK:  HTTP update fatal error code %d\n", err);
}

/**
 * 固件升级函数
 * 在需要升级的地方，加上这个函数即可，例如setup中加的updateBin();
 * 原理：通过http请求获取远程固件，实现升级
 */
// TaskHandle_t TASK_HandleOne = NULL;

void updateBin()
{
  String upUrl = "http://bin.bemfa.com/b/3BcNDg5MmJkN2ZlMDA1ZWNiYmNjZjM1OTI5MTU3ZWM3ZTc=" + TOPIC1 + ".bin";

  // 初始化看门狗定时器
  tim1 = timerBegin(1, 80, true);
  timerAttachInterrupt(tim1, tim1Interrupt, true);
  timerAlarmWrite(tim1, 30000 * 1000, true); // 30s触发
  timerAlarmEnable(tim1);
  // Serial.println("start update");
  WiFiClient UpdateClient;
  httpUpdate.onStart(update_started);     // 当升级开始时
  httpUpdate.onEnd(update_finished);      // 当升级结束时
  httpUpdate.onProgress(update_progress); // 当升级中
  httpUpdate.onError(update_error);       // 当升级失败时

  t_httpUpdate_return ret = httpUpdate.update(UpdateClient, upUrl);
  switch (ret)
  {
  case HTTP_UPDATE_FAILED: // 当升级失败
    // Serial.println("[update] Update failed.");
    break;
  case HTTP_UPDATE_NO_UPDATES: // 当无升级
    // Serial.println("[update] Update no Update.");
    break;
  case HTTP_UPDATE_OK: // 当升级成功
    // Serial.println("[update] Update ok.");
    break;
  }
}

// 按钮配置接口
/******单击******/
void click()
{
  // Serial.println("单击");
  lanyasaomian();
  if (State3)
  {
    turnOnLed();
  }
}
/******双击******/
void doubleclick()
{
  // Serial.println("双击");
  if (State1)
  {
    turnOnJlm(); // 打开灯
    delay(500);
    turnOffJlm(); // 打开灯
  }

}
/******长按******/
void duringLongPress()
{
  // Serial.println("长按");
  restoreFactory();
}

Ticker delayTimer;
void delayRestart(float t)
{
  delayTimer.attach(t, []()
                    { ESP.restart(); });
}
void getUid(String mac, bool reConfig)
{
  if (strcmp(config.cuid, "4892bd7fe005ecbbccf35929157ec7e7") != 0 || reConfig)
  {
    // Serial.println("读取私钥...");
    http_bemfa_HTTPClient.begin(client_bemfa_WiFiClient, "http://api.bemfa.com/api/device/v1/airkiss/?topic=" + mac + "&version=" + verSion + "&ad=" + adminID);
    httpCode = http_bemfa_HTTPClient.GET();
    if (httpCode > 0)
    {
      String payload = http_bemfa_HTTPClient.getString();
      // json数据解析
      StaticJsonDocument<200> doc;
      DeserializationError error = deserializeJson(doc, payload);
      if (error)
      {
        // Serial.print(F("de//SerializeJson() failed: "));
        // Serial.println(error.c_str());
      }
      String code = doc["code"];
      if (code == "5723200")
      {

        String docUID = doc["uid"];
        strcpy(config.cuid, docUID.c_str());
        strcpy(config.ctopic, mac.c_str());
        saveConfig();
      }
      else
      {
        // Serial.println(" config ERROR.........");
      }

      http_bemfa_HTTPClient.end();
    }
  }
  if (strcmp(config.ctopic, "") == 0)
  {
    TOPIC1 = mac;
  }
  else
  {
    TOPIC1 = config.ctopic;
  }
  UID = config.cuid;
  // Serial.printf("密钥:%s \n", config.cuid);
  // Serial.printf("主题:%s \n", config.ctopic);
  // Serial.printf("网络:%s \n", config.stassid);
  // Serial.printf("密码:%s \n", config.stapsw);
}
/**
 * 加载存储的信息，并检查是否进行了反复5次重启恢复出厂信息
 */
void loadConfig()
{
  uint8_t mac[6];
  WiFi.macAddress(mac);
  EEPROM.begin(256);
  for (int i = 0; i < sizeof(config); i++)
  {
    *(p + i) = EEPROM.read(i);
  }
  Serial.printf("密钥:%s \n", config.cuid);
  Serial.printf("主题:%s \n", config.ctopic);
  Serial.printf("网络:%s \n", config.stassid);
  Serial.printf("密码:%s \n", config.stapsw);
  Serial.printf("IP地址1:%s \n", config.ip_1);
  Serial.printf("IP地址2:%s \n", config.ip_2);
  config.reboot = config.reboot + 1;
  if (config.reboot >= 10)
  {
    restoreFactory();
  }
  if (config.magic != 0xAA)
  {
    config_flag = 1;
  }
  EEPROM.begin(256);
  for (int i = 0; i < sizeof(config); i++) // 写数据
  {
    EEPROM.write(i, *(p + i)); // 写数据
  }
  EEPROM.commit(); // 保存更改的数据
  delay(500);
  EEPROM.begin(256);
  config.reboot = 0;
  for (int i = 0; i < sizeof(config); i++)
  {
    EEPROM.write(i, *(p + i));
  }
  EEPROM.commit();
}
/**
 * 恢复出厂设置，清除存储的wifi信息
 */
void restoreFactory()
{
  // Serial.println("Restore Factory....... ");
  config.magic = 0x00;
  strcpy(config.stassid, "");
  strcpy(config.stapsw, "");
  strcpy(config.cuid, "");
  strcpy(config.ctopic, "");
  strcpy(config.ip_1, "");
  strcpy(config.ip_2, "");
  saveConfig();
  delayRestart(1);
  while (1)
  {
    delay(100);
  }
}
/**
 * 检查是否需要airkiss配网
 */
void waitKey()
{
  if (config_flag == 1)
  {
    doSmartconfig();
  }
}
/**
 * airkiss配网
 */
void doSmartconfig()
{
  // Serial.println("正在等待配网...");
  WiFi.disconnect();
  WiFi.mode(WIFI_STA);
  WiFi.stopSmartConfig();
  WiFi.beginSmartConfig();
  while (!WiFi.smartConfigDone())
  {
    delay(100);
  }
  strcpy(config.stassid, WiFi.SSID().c_str());
  strcpy(config.stapsw, WiFi.psk().c_str());
  config.magic = 0xAA;
  saveConfig();
  // Serial.println("配网成功");
  WiFi.disconnect();                         // 断开连接
  WiFi.mode(WIFI_STA);                       // STA模式
  WiFi.begin(config.stassid, config.stapsw); // 连接路由器
  // Serial.println("正在连接网络...");
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(1000);
  }
  delay(1000);
  // Serial.println("连接成功");
}

/**
 * 存储配网信息
 */
void saveConfig()
{
  uint8_t mac[6];
  WiFi.macAddress(mac);
  config.reboot = 0;
  EEPROM.begin(256);
  uint8_t *p = (uint8_t *)(&config);
  for (int i = 0; i < sizeof(config); i++)
  {
    EEPROM.write(i, *(p + i));
  }
  EEPROM.commit();
}
/**
 * 初始化wifi信息，并连接路由器网络
 */
void initWiFi()
{
  if (WiFi.status() != WL_CONNECTED)
  {
    WiFi.disconnect();                         // 断开连接
    WiFi.mode(WIFI_STA);                       // STA模式
    WiFi.begin(config.stassid, config.stapsw); // 连接路由器
    // Serial.println("正在连接网络...");
  }
}

/*
  WiFiTick
  检查是否需要初始化WiFi
  检查WiFi是否连接上，若连接成功启动TCP Client
  控制指示灯
*/
void doWiFiTick()
{
  static bool taskStarted = false;
  static uint32_t lastWiFiCheckTick = 0;

  if (WiFi.status() != WL_CONNECTED) // 检查是否连接成功
  {
    if (millis() - lastWiFiCheckTick > 10000) // 未连接重连
    {
      Serial.println("网络未连接重连");
      initWiFi();
      taskStarted = false;
      lastWiFiCheckTick = millis();
    }
  }
  else if (taskStarted == false)
  {
    Serial.println("网络连接成功");
    Serial.print("IP地址:");
    Serial.println(WiFi.localIP());
    taskStarted = true;
    startTCPClient();
  }
}

String convert(int State)
{
  if (State)
  {
    return "on";
  }
  else
  {
    return "off";
  }
}
void sendtoTCPServer()
{
  sendtoTCPServer("cmd=2&uid=" + UID + "&topic=" + TOPIC1 + "/up&msg=" + convert(State) + "#" + convert(State1) + "#" + convert(State2) + "#" + convert(State3) + "\r\n"); // 推送消息
}

/*
 *发送数据到TCP服务器
 */
void sendtoTCPServer(String p)
{
  if (!TCPclient.print(p))
  {
    // Serial.println("TCPclien发送失败");
    TCPclient.stop();
  }
  preHeartTick = millis(); // 心跳计时开始，需要每隔60秒发送一次数据
}
/*
 *初始化和服务器建立连接
 */
void startTCPClient()
{
  Serial.println("TCPclien开始连接");
  if (TCPclient.connect(server_ip, atoi(server_port)))
  {

    String tcpTemp = "";                                                       // 初始化字符串
    tcpTemp = "cmd=1&uid=" + UID + "&topic=" + TOPIC1 + "," + TOPIC2 + "\r\n"; // 构建订阅指令
    sendtoTCPServer(tcpTemp);                                                  // 发送订阅指令
    Serial.println("发送订阅指令：");
    Serial.println(tcpTemp);
    tcpTemp = ""; // 清空
    /*
     //如果需要订阅多个主题，可发送  cmd=1&uid=xxxxxxxxxxxxxxxxxxxxxxx&topic=xxx1,xxx2,xxx3,xxx4\r\n
    教程：https://bbs.bemfa.com/64
     */
    TCPclient.setNoDelay(true);
  }
  else
  {
    failCount = failCount + 1;
    if (failCount > 10) // 如果失败连接10次
    {
      failCount = 0;
      WiFi.disconnect(); // 断开连接
    }
    // Serial.println();
    // Serial.println("TCPclien连接失败");
    TCPclient.stop();
  }
  preTCPStartTick = millis();
}
/*
 *检查数据，发送心跳
 */
void doTCPClientTick()
{
  // 检查是否断开，断开后重连
  if (WiFi.status() != WL_CONNECTED)
  {
    preTCPStartTick = millis();
    return;
  }
  if (!TCPclient.connected())
  {                                        // 断开重连
    if (millis() - preTCPStartTick > 3000) // 重新连接
    {
      startTCPClient();
    }
  }
  else
  {
    if (TCPclient.available())
    { // 收数据
      char c = TCPclient.read();
      TcpClient_Buff += c;
      TcpClient_BuffIndex++;
      TcpClient_preTick = millis();

      if (TcpClient_BuffIndex >= MAX_PACKETSIZE - 1)
      {
        TcpClient_BuffIndex = MAX_PACKETSIZE - 2;
        TcpClient_preTick = TcpClient_preTick - 200;
      }
    }
    if (millis() - preHeartTick >= KEEPALIVEATIME)
    {
      // 保持心跳
      sendtoTCPServer("cmd=0&msg=keep\r\n");
      State = digitalRead(StatePin);
      if (State != State1)
      {
        if (State == 0)
        {
          turnOffLed();
        }
        else
        {
          turnOnLed();
        }
        State1 = State;
        sendtoTCPServer();
      }
    }
  }
  if ((TcpClient_Buff.length() >= 20) && (millis() - TcpClient_preTick >= 200))
  {
    TCPclient.flush();
    if (TcpClient_Buff.indexOf("f5") > 0)
    {
      sendtoTCPServer();
    }

    else if (TcpClient_Buff.indexOf("update") > 0)
    {
      // Serial.println("[update] Update Start......");
      updateBin();
    }

    else if (TcpClient_Buff.indexOf("on") > 0 && TcpClient_Buff.indexOf(TOPIC2) > 0) // 如果是消息==打开
    {
      turnOnJlm(); // 打开灯
      preHeartTick = 0;
      sendtoTCPServer();
    }

    else if (TcpClient_Buff.indexOf("off") > 0 && TcpClient_Buff.indexOf(TOPIC2) > 0) // 如果是消息==关闭
    {
      turnOffJlm(); // 关闭灯
      preHeartTick = 0;
      sendtoTCPServer();
    }

    else if (TcpClient_Buff.indexOf("on") > 0) // 如果是消息==打开
    {
      turnOnLed(); // 打开灯
      preHeartTick = 0;
      sendtoTCPServer();
    }

    else if (TcpClient_Buff.indexOf("off") > 0) // 如果是消息==关闭
    {
      turnOffLed(); // 关闭灯
      preHeartTick = 0;
      sendtoTCPServer();
    }

    else if (TcpClient_Buff.indexOf("canshu") > 0 || TcpClient_Buff.indexOf("ip") > 0)
    {
      int a = TcpClient_Buff.indexOf("ip1=");
      int b = TcpClient_Buff.indexOf("ip2=");
      if (a > 0)
      {
        int c = TcpClient_Buff.indexOf("\n", a);
        String VaLue = TcpClient_Buff.substring(a + 4, c);
        strcpy(config.ip_1, VaLue.c_str());
        // Serial.printf("地址1:%s\n", config.ip_1);
        saveConfig();
      }
      else if (b > 0)
      {
        int c = TcpClient_Buff.indexOf("\n", b);
        String VaLue = TcpClient_Buff.substring(b + 4, c);
        strcpy(config.ip_2, VaLue.c_str());
        // Serial.printf("地址2:%s\n", config.ip_2);
        saveConfig();
      }
      sendtoTCPServer("cmd=2&uid=" + UID + "&topic=" + TOPIC1 + "/up&msg=canshu#" + config.ip_1 + "#" + config.ip_2 + "\r\n"); // 推送消息
      delay(2000);
      sendtoTCPServer();
    }
    TcpClient_Buff = "";
    TcpClient_BuffIndex = 0;
  }
}

// 开小门
void turnOnLed()
{
  // Serial.println("Turn ON");
  digitalWrite(k_Pin, HIGH);
}
// 关小门
void turnOffLed()
{
  // Serial.println("Turn OFF");
  digitalWrite(k_Pin, LOW);
}

// 打开卷帘门
void turnOnJlm()
{
  // Serial.println("k2 LOW");
  digitalWrite(k2_Pin, LOW);
  delay(500);
  digitalWrite(k2_Pin, HIGH);
  // Serial.println("k2 HIGH");
}
// 关闭卷帘门
void turnOffJlm()
{
  // Serial.println("k1 LOW");
  digitalWrite(k1_Pin, LOW);
  delay(500);
  digitalWrite(k1_Pin, HIGH);
  // Serial.println("k1 HIGH");
}

// 蓝牙扫描
void lanyasaomian()
{
  BLEScanResults foundDevices = pBLEScan->start(1); // 启动扫描2秒，该方式此处会阻塞直到扫描结束
}

class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks
{
  void onResult(BLEAdvertisedDevice advertisedDevice)
  {
    String get = advertisedDevice.getServiceDataUUID().toString().c_str();
    if (get == getb)
    {
      State3 = true;
      // Serial.println("找到蓝牙设备");
      // int W = advertisedDevice.getRSSI();
      // Serial.printf("信号强度:%d \n", W);
      //  //Serial.printf("Advertised Device: %s \n", advertisedDevice.toString().c_str()); // 打印设备信息
      advertisedDevice.getScan()->stop();
      pBLEScan->clearResults(); // 清除扫描结果释放空间
    }
    // else
    // {
    //   W = 0;
    // }
  }
};
void task(void *parameter)
{

  while (true)
  {
    int Z = 0;
    delay(KEEPALIVEATIME);
    while (WiFi.status() == WL_CONNECTED && config.ip_1 != "" && config.ip_2 != "")
    {
      if (Ping.ping(config.ip_1, 1) || Ping.ping(config.ip_2, 1))
      {
        // Serial.println("回家");
        // Serial.println("开锁");
        if (Z == 0)
        {
          State2 = true;
          State3 = true;
        }
        while ((Ping.ping(config.ip_1, 1) || Ping.ping(config.ip_2, 1)) && !State)
        {
          Z++;
          if (Z == 60)
          {
            State3 = false;
            // Serial.println("关锁");
          }
          delay(KEEPALIVEATIME);
        }
      }
      else if (State2 == true)
      {
        // Serial.println("离家");
        Z = 0;
        State2 = false;
        State3 = false;
      }
      delay(KEEPALIVEATIME);
    }
  }
  vTaskDelete(NULL);
}
void Autosuo(void)
{
  xTaskCreate(
      task,   /* Task function. */
      "task", /* String with name of task. */
      10000,  /* Stack size in bytes. */
      NULL,   /* Parameter passed as input of the task */
      1,      /* Priority of the task. */
      NULL);  /* Task handle. */
}

// 初始化，相当于main 函数
void setup()
{
  Serial.begin(115200);
  Serial.println("初始化...");
  loadConfig();
  waitKey();
  initWiFi();
  Autosuo();
  String TOPIC = WiFi.macAddress().substring(8); // 取mac地址做主题用
  TOPIC.replace(":", "");                        // 去掉:号
  TOPIC1 = TOPIC + "004";
  TOPIC2 = TOPIC + "006";
  getUid(TOPIC1, false);
  pinMode(k_Pin, OUTPUT);
  pinMode(k0_Pin, OUTPUT);
  pinMode(k1_Pin, OUTPUT);
  pinMode(k2_Pin, OUTPUT);
  pinMode(StatePin, INPUT);
  pinMode(buttonPin, INPUT_PULLUP);
  digitalWrite(k_Pin, LOW);
  digitalWrite(k0_Pin, HIGH);
  digitalWrite(k1_Pin, HIGH);
  digitalWrite(k2_Pin, HIGH);

  // 按键配置
  button.reset();                               // 清除按钮状态机的状态
  button.attachClick(click);                    // 注册单击
  button.attachDoubleClick(doubleclick);        // 注册双击
  button.attachLongPressStart(duringLongPress); // 注册长按开始
  button.setPressTicks(10000);                  // 设置长按时间
  // button.attachDuringLongPress(duringLongPress); // 注册长按
  // button.attachLongPressStop(longPressStop); // 注册长按结束
  // button.attachDuringLongPress(attachPressStart);//按下键就会持续触发

  // 蓝牙配置
  BLEDevice::init("");
  pBLEScan = BLEDevice::getScan();                                           // 获取扫描对象
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); // 添加搜索到设备Advertising广播时的回调函数
  pBLEScan->setActiveScan(true);                                             // 启用主动扫描
  pBLEScan->setInterval(100);                                                // 相邻两次扫描开始时间间隔（单位：毫秒）
  pBLEScan->setWindow(80);                                                   // 一次扫描持续时间（单位：毫秒）
                                                                             // 这个参数必须小于等于setInterval()中的参数
                                                                             // 如果这个参数等于setInterval()中的参数则表示不间断的连续扫描
}
void loop()
{
  delay(1);
  button.tick();
  doWiFiTick();
  doTCPClientTick(); // tcp消息接收
}